package io.gitee.devaper.starlight.module.room.websocket

import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.module.kotlin.*
import io.gitee.devaper.starlight.common.entity.po.*
import io.gitee.devaper.starlight.module.room.*
import io.gitee.devaper.starlight.module.room.constant.*
import io.gitee.devaper.starlight.module.room.websocket.event.client.*
import io.gitee.devaper.starlight.module.room.websocket.event.server.*
import org.springframework.stereotype.Component
import org.springframework.web.socket.*
import java.util.ArrayList
import java.util.regex.Pattern

/**
 * 客户端消息处理器, 用来处理客户端主动发送的消息
 */
@Component
open class ClientMessageHandler(
    private val roomRepository: RoomRepository,
    private val objectMapper: ObjectMapper
) {

    data class Context(
        val room: Room,
        val sender: WebSocketSession,
        val senderUser: UserPO,
        val type: String,
        val details: JsonNode
    ) {
        val isCreator get() = room.creator.id == senderUser.id
    }

    fun handle(context: Context) {
        when (context.type) {
            MessageClientEventDetails.eventType()               -> handleMessageEvent(context)
            VideoStateClientEventDetails.eventType()            -> handleVideoStateEvent(context)
            VideoSeekedClientEventDetails.eventType()           -> handleVideoSeekedEvent(context)
            VideoSpeedClientEventDetails.eventType()            -> handleVideoSpeedEvent(context)
            VideoChangeClientEventDetails.eventType()           -> handleVideoChangeEvent(context)
            RoomDissolveClientEventDetails.eventType()          -> handleRoomDissolveEvent(context)
            RoomModifyNameClientEventDetails.eventType()        -> handleRoomModifyName(context)
            RoomModifyPasswordClientEventDetails.eventType()    -> handleRoomModifyPassword(context)
        }
    }


    private fun handleMessageEvent(context: Context) {
        val details = objectMapper.convertValue<MessageClientEventDetails>(context.details)
        // 消息内容为空则忽略
        if (details.message.isEmpty()) {
            return
        }
        // 消息过长则忽略
        if (details.message.length > MESSAGE_MAX_LENGTH) {
            return
        }

        val message = ServerEvent(
            details = MessageServerEventDetails(
                sender = context.senderUser.id,
                message = details.message
            )
        ).toTextMessage()

        context.room.broadcast(message, useLock = true)
    }

    private fun handleVideoStateEvent(context: Context) {
        val details = objectMapper.convertValue<VideoStateClientEventDetails>(context.details)

        context.room.video.updateState(details.state)

        val message = ServerEvent(
            details = VideoStateServerEventDetails(
                operator = context.senderUser.id,
                state = details.state
            )
        ).toTextMessage()

        context.room.broadcast(message, ignore = context.sender, useLock = true)
    }

    private fun handleVideoSeekedEvent(context: Context) {
        val details = objectMapper.convertValue<VideoSeekedServerEventDetails>(context.details)

        context.room.video.updateTime(details.time)

        val message = ServerEvent(
            details = VideoSeekedServerEventDetails(
                operator = context.senderUser.id,
                time = details.time
            )
        ).toTextMessage()

        context.room.broadcast(message, ignore = context.sender, useLock = true)
    }

    private fun handleVideoSpeedEvent(context: Context) {
        val details = objectMapper.convertValue<VideoSpeedClientEventDetails>(context.details)

        context.room.video.updateSpeed(details.speed!!)

        val message = ServerEvent(
            details = VideoSpeedServerEventDetails(
                operator = context.senderUser.id,
                speed = details.speed!!
            )
        ).toTextMessage()

        context.room.broadcast(message, ignore = context.sender, useLock = true)
    }

    private fun handleVideoChangeEvent(context: Context) {
        if (!context.isCreator) {
            return handleIllegalOperation(context)
        }

        val details = objectMapper.convertValue<VideoChangeClientEventDetails>(context.details)

        context.room.video.changeVideo(details.url)

        val message = ServerEvent(
            details = VideoChangeServerEventDetails(
                url = details.url
            )
        ).toTextMessage()

        context.room.broadcast(message, useLock = true)

    }

    private fun handleRoomDissolveEvent(context: Context) {
        if (!context.isCreator) {
            return handleIllegalOperation(context)
        }

        val candidates = ArrayList<WebSocketSession>(context.room.clients.size)
        context.room.write {
            context.room.capacity = 0
            candidates.addAll(it.clients)
            roomRepository.write {
                // 将房间删除
                roomRepository.map.remove(it.id)
            }
        }
        candidates.forEach { candidate ->
            if (candidate.isOpen) {
                try {
                    candidate.close(WS_ROOM_CLOSED_CLOSE_STATUS)
                } catch (ignored: Exception) {
                }
            }
        }
    }

    private fun handleRoomModifyName(context: Context) {
        if (!context.isCreator) {
            return handleIllegalOperation(context)
        }

        val details = objectMapper.convertValue<RoomModifyNameClientEventDetails>(context.details)
        if (Pattern.matches(ROOM_NAME_PATTERN, details.name)) {
            context.room.write {
                it.name = details.name
            }
        }
    }

    private fun handleRoomModifyPassword(context: Context) {
        if (!context.isCreator) {
            return handleIllegalOperation(context)
        }

        val details = objectMapper.convertValue<RoomModifyPasswordClientEventDetails>(context.details)
        // 密码为空或者符合规则均可
        if (details.pwd.isEmpty() || Pattern.matches(ROOM_PASSWORD_PATTERN, details.pwd)) {
            context.room.write {
                it.password = details.pwd.takeIf { it.isNotBlank() }
            }
        }
    }

    /**
     * 处理非法操作
     *
     * @param context context
     * @param disconnect 是否断开该客户端连接
     */
    private fun handleIllegalOperation(context: Context, disconnect: Boolean = false) {
        if (disconnect) {
            context.sender.close(CloseStatus.NOT_ACCEPTABLE)
        }
    }

    private fun ServerEvent.toTextMessage() =
        TextMessage(objectMapper.writeValueAsString(this))

}